package org.biogroovy.io.clinicaltrials

import groovy.json.JsonSlurper
import groovy.util.slurpersupport.NodeChild
import groovy.util.slurpersupport.NodeChildren

import org.biogroovy.io.AbsXmlReader
import org.biogroovy.io.IFetcher;
import org.biogroovy.models.ClinicalTrial
import org.biogroovy.models.ClinicalTrialEligibility
import org.biogroovy.models.ClinicalTrialIntervention
import org.biogroovy.models.ClinicalTrialInterventionType
import org.biogroovy.models.ClinicalTrialOutcome
import org.biogroovy.models.ClinicalTrialSponsor
import org.biogroovy.models.MeshHeading
import org.w3c.dom.Node

/**
 * This class is responsible for reading data from the ClinicalTrials.gov RESTful service.
 * http://clinicaltrials.gov/ct2/resources/download
 *
 */
class ClinicalTrialReader extends AbsXmlReader<ClinicalTrial> {
	
	/** The database name. */
	static final String DATABASE_NAME="clinicaltrials";
		
	/**
	 * Constructor.
	 */
	public ClinicalTrialReader(){
		this.databaseName = DATABASE_NAME;
	}
	

	@Override
	public void parse(ClinicalTrial trial, Node node) {
		trial.clinicalTrialId = node.'id_info'.'nct_id';
		trial.briefTitle = node.'brief_title';
		trial.officialTitle = node.'official_title';
		trial.clinicalTrialUri = node.'required_header'.'url';
		trial.briefSummary = node.'brief_summary'.'textblock';
		trial.detailedDescription = node.'detailed_description'.'textblock';
		
	}
	
	/**
	 * This method is responsible for parsing a clinical trial object.
	 * @param node the clinical trial node
	 * @return a ClinicalTrial object.
	 */
	public ClinicalTrial parse(NodeChild node){
		ClinicalTrial trial = new ClinicalTrial();
		trial.clinicalTrialId = node.'id_info'.'nct_id';
		trial.briefTitle = node.'brief_title';
		trial.officialTitle = node.'official_title';
		trial.clinicalTrialUri = node.'required_header'.'url';
		trial.briefSummary = node.'brief_summary'.'textblock';
		trial.detailedDescription = node.'detailed_description'.'textblock';
		
		trial.collaborators = parseCollaborators(node);
		trial.sponsors = parseSponsors(node);
		trial.meshTerms = parseMeshTerms(node);
		trial.tags = parseTags(node);
		
		trial.primaryOutcomes = parseOutcomes(node);
		trial.interventions = parseInterventions(node);
		trial.phase = node.'phase';
		trial.startDate = node.'start_date';
		trial.primaryCompletionDate = node.'primary_completion_date';
		trial.studyType = node.'study_type';
		trial.studyDesign = node.'study_design';
		trial.overallStatus = node.'overall_status';
		
		trial.eligibility = parseEligibility(node);
		
		return trial;

	}
	
	/**
	 * This method parses clinical trial elibility information.
	 * @param node the node to be parsed.
	 * @return a list of ClinicalTrialEligibility 
	 */
	private List<ClinicalTrialEligibility> parseEligibility(NodeChild node){
		List<ClinicalTrialEligibility> eligList = new ArrayList<>();
		
		NodeChildren children = node.'eligibility';
		children.list().each{
			ClinicalTrialEligibility elig = new ClinicalTrialEligibility();
			elig.criteria = it.criteria.textblock;
			elig.gender = elig.lookupGender(it.gender.text());
			elig.healthyVolunteers = it.'healthy_volunteers';
			elig.minAge = it.'minimum_age';
			elig.maxAge = it.'maximum_age';
			elig.samplingMethod = it.'sampling_method';
			elig.studyPop = it.'study_pop'.'textblock';
			
			eligList.add(elig);
		}
		
		return eligList;
	}
	
	/**
	 * This method parses the clinical trial interventions.
	 * @param node the intervention node
	 * @return a list of clinical trial interventions
	 */
	private List<ClinicalTrialIntervention> parseInterventions(NodeChild node){
		List<ClinicalTrialIntervention> interventions = new ArrayList<>();
		
		NodeChildren children = node.'intervention';
		children.list().each{
			ClinicalTrialIntervention inter = new ClinicalTrialIntervention();
			inter.description = it.'description';
			inter.name = it.'intervention_name';
			String typeStr = it.'intervention_type';
			inter.type = ClinicalTrialInterventionType.lookup(typeStr);
			interventions.add(inter);
		}
		
		return interventions;
	}
	
	/**
	 * This method parses the clinical trial outcomes.
	 * @param node the node containing the outcomes.
	 * @return a list of clinical trial outcomes
	 */
	private List<ClinicalTrialOutcome> parseOutcomes(NodeChild node){
		List<ClinicalTrialOutcome> outcomes = new ArrayList<>();
		NodeChildren children = node.'primary_outcome';
		children.list().each{
			ClinicalTrialOutcome outcome = new ClinicalTrialOutcome();
			outcome.measure = it.measure;
			outcome.safetyIssue = it.'safety_issue';
			outcome.description = it.'description';
			outcome.timeFrame = it.'time_frame';
			outcomes.add(outcome);
		}
		
		return outcomes;
	}
	
	/**
	 * This method parses the collaborators in the clinical trial
	 * @param node the collaborators node
	 * @return a list of clinical trial collaborators
	 */
	private List<ClinicalTrialSponsor> parseCollaborators(NodeChild node){
		List<ClinicalTrialSponsor> collabList = new ArrayList<>();
		NodeChildren children = node.sponsors.collaborator;
		children.list().each{ 
			ClinicalTrialSponsor collab = new ClinicalTrialSponsor();
			collab.agency = it.agency;
			collab.agencyClass = it.agency_class;
			collab.sponsorType = ClinicalTrialSponsor.SponsorType.COLLABORATOR;
			collabList.add(collab);
		}
		return collabList;
	}
	
	/**
	 * This method parses the clinical trial sponsor information
	 * @param node the sponsors node
	 * @return a list of clinical trial sponsors.
	 */
	private List<ClinicalTrialSponsor> parseSponsors(NodeChild node){
		List<ClinicalTrialSponsor> sponsorList = new ArrayList<>();
		NodeChildren children = node.sponsors.'lead_sponsor';
		children.list().each{
			ClinicalTrialSponsor collab = new ClinicalTrialSponsor();
			collab.agency = it.agency;
			collab.agencyClass = it.agency_class;
			collab.sponsorType = ClinicalTrialSponsor.SponsorType.LEAD_SPONSOR;
			sponsorList.add(collab);
		}
		return sponsorList;

	}
	
	/**
	 * This method parses the mesh terms.
	 * @param node the MeSH term node.
	 * @return a list of MeSH Headings
	 */
	private List<MeshHeading> parseMeshTerms(NodeChild node){
		List<MeshHeading> meshTerms = new ArrayList<>();
		NodeChildren children = node.'condition_browse'.'mesh_term';
		children.list().each{
			MeshHeading term = new MeshHeading();
			term.descriptorName = it;
			meshTerms.add(term);
		}
	}
	
	/**
	 * This method parses the keyword tags.
	 * @param node the node containing the tags
	 * @return a list of keywords
	 */
	private List<String> parseTags(NodeChild node){
		List<String> tags = new ArrayList<>();
		NodeChildren children = node.'keyword';
		children.list().each{ 
			tags.add(it);
		}
	}

	@Override
	public ClinicalTrial read(InputStream inputStream) throws IOException {
		XmlSlurper slurper = new XmlSlurper();
		slurper.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
		def root  = slurper.parse(inputStream)

		ClinicalTrial trial = parse(root);
		
		return trial;
	}

	@Override
	public List<ClinicalTrial> readList(InputStream inputStream)
			throws IOException {
		
		List<ClinicalTrial> trialList = new ArrayList<>();
		
		XmlSlurper slurper = new XmlSlurper();
		slurper.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
		def root  = slurper.parse(inputStream);
		
		NodeChildren children = root.'clinical_study';
		children.list().each{
			ClinicalTrial trial = new ClinicalTrial();
			trial.clinicalTrialId = it.'nct_id';
			trial.briefTitle = it.'title';
			trial.overallStatus = it.'status';
			trial.clinicalTrialUri = it.'utl';
			trialList.add(trial);
		}

		
		return trialList;
	}

	@Override
	public ClinicalTrial fetch(String id) throws IOException {
		URL url = getUrl(id, null)
		ClinicalTrial trial = read(url.openStream())
		return trial;
	}

	@Override
	public URL getUrl(String id, Map<String, String> paramMap) {		
		String urlStr = "http://clinicaltrials.gov/show/${id}?displayxml=true"
		URL url = new URL(urlStr);
		return url;
	}



	@Override
	public List<ClinicalTrial> fetchAll(String id) throws IOException {
		String[] idArray = id.split(",")
		List<ClinicalTrial> trialList = new ArrayList<>()
		idArray.each{String currId ->
			ClinicalTrial trial = fetch(currId.trim())
			trialList.add(trial)
		}
		return trialList;
	}


	@Override
	public IFetcher<ClinicalTrial> getNewInstance() {
		return new ClinicalTrialReader();
	}

}
